/*
 * Copyright (C) 2019 ByteDance Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bytedance.scene.navigation;


import com.bytedance.scene.*;
import com.bytedance.scene.animation.NavigationAnimationExecutor;
import com.bytedance.scene.animation.animatorexecutor.DefaultSceneAnimatorExecutor;
import com.bytedance.scene.animation.interaction.InteractionNavigationPopAnimationFactory;
import com.bytedance.scene.group.ReuseGroupScene;
import com.bytedance.scene.interfaces.*;
import com.bytedance.scene.utlity.*;
import com.bytedance.scene.view.AnimationContainerLayout;
import com.bytedance.scene.view.NavigationFrameLayout;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.ILifecycle;
import ohos.aafwk.ability.Lifecycle;
import ohos.aafwk.ability.LifecycleStateObserver;
import ohos.aafwk.ability.fraction.FractionAbility;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.IntentParams;
import ohos.aafwk.content.Operation;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.ShapeElement;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by JiangQi on 7/30/18.
 *
 * (NavigationScene cannot be inherited)
 *
 * When entering:
 *
 *                                                                                         +-------------------------+
 *                                                                                         |      Child onStart      |
 *                                                                                         +-------------------------+
 *                                                                                           ^
 *                                                                                           | sync
 *                                                                                           |
 * +--------------------------+     +--------------------+     +---------------------+     +-------------------------+     +-----------------+     +----------------+
 * | Parent onAbilityCreated | --> |       sync_1       | --> |   Parent onStart    | --> |         sync_2          | --> | Parent onResume | --> |     sync_3     | ---
 * +--------------------------+     +--------------------+     +---------------------+     +-------------------------+     +-----------------+     +----------------+
 *                                    |                                                                                                              |
 *                                    | sync                                                                                                         | sync
 *                                    v                                                                                                              v
 *                                  +--------------------+     +---------------------+     +-------------------------+                             +----------------+
 *                                  | Child onCreateView | --> | Child onViewCreated | --> | Child onAbilityCreated |                             | Child onResume |
 *                                  +--------------------+     +---------------------+     +-------------------------+                             +----------------+
 *
 *
 *
 * When exiting:
 *
 * +---------------+     +----------------+     +--------------+     +---------------+     +---------------------+     +----------------------+
 * | Child onPause | --> |     sync_1     | --> | Child onStop | --> |    sync_2     | --> | Child onDestroyView | --> |        sync_3        | ---
 * +---------------+     +----------------+     +--------------+     +---------------+     +---------------------+     +----------------------+
 *                         |                                           |                                                 |
 *                         | sync                                      | sync                                            | sync
 *                         v                                           v                                                 v
 *                       +----------------+                          +---------------+                                 +----------------------+
 *                       | Parent onPause |                          | Parent onStop |                                 | Parent onDestroyView |
 *                       +----------------+                          +---------------+                                 +----------------------+
 *
 */
public final class NavigationScene extends Scene implements NavigationListener, SceneParent, SuppressOperationAware{
    private static NavigationScene mInstance;

    public static NavigationScene getInstance() {
        return mInstance;
    }

    public static void setInstance(NavigationScene mInstance) {
        NavigationScene.mInstance = mInstance;
    }

    private static final String KEY_NAVIGATION_SCENE_SUPPORT_RESTORE_ARGUMENT = "bd-scene-navigation:support_restore";

    private boolean mSupportRestore = true;
    private SceneComponentFactory mRootSceneComponentFactory;
    NavigationSceneOptions mNavigationSceneOptions;

    private NavigationSceneManager mNavigationSceneManager;
    private StackLayout mSceneContainer;
    private StackLayout mAnimationContainer;
    
    private NavigationAnimationExecutor mDefaultNavigationAnimationExecutor = new DefaultSceneAnimatorExecutor();
    private final List<InteractionNavigationPopAnimationFactory.InteractionCallback> mInteractionListenerList = new ArrayList<>();

    private final Map<Class, ReuseGroupScene> mLruCache = new LinkedHashMap<>(3);

    private final List<NavigationListener> mNavigationListenerList = new ArrayList<>();
    private final List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> mLifecycleCallbacks = new ArrayList<>();
    private static AbilityResultCallback resultCallback;
    private PermissionResultCallback permissionResultCallback;

    public static AbilityResultCallback getResultCallback() {
        return resultCallback;
    }

    public static void setResultCallback(AbilityResultCallback resultCallback) {
        NavigationScene.resultCallback = resultCallback;
    }

    public void addNavigationListener(final ILifecycle lifecycleOwner, final NavigationListener listener) {
        ThreadUtility.checkUIThread();
        if (lifecycleOwner.getLifecycle().getLifecycleState() == Lifecycle.Event.ON_STOP) {
            // ignore
            return;
        }
        this.mNavigationListenerList.add(listener);
        lifecycleOwner.getLifecycle().addObserver(new LifecycleStateObserver() {
            @Override
            public void onStateChanged(Lifecycle.Event event, Intent intent) {
                if(Lifecycle.Event.ON_STOP.toString().equals(event.name())){
                    lifecycleOwner.getLifecycle().removeObserver(this);
                    mNavigationListenerList.remove(listener);
                }
            }
        });
    }

    
    public void removeNavigationListener(NavigationListener listener) {
        ThreadUtility.checkUIThread();
        this.mNavigationListenerList.remove(listener);
    }

    
    public void addOnBackPressedListener(final ILifecycle lifecycleOwner, final OnBackPressedListener onBackPressedListener) {
        ThreadUtility.checkUIThread();
        if (lifecycleOwner.getLifecycle().getLifecycleState() == Lifecycle.Event.ON_STOP) {
            // ignore
            return;
        }
        this.mNavigationSceneManager.addOnBackPressedListener(lifecycleOwner, onBackPressedListener);

        lifecycleOwner.getLifecycle().addObserver(new LifecycleStateObserver() {
            @Override
            public void onStateChanged(Lifecycle.Event event, Intent intent) {
                if(Lifecycle.Event.ON_STOP.toString().equals(event.name())){
                    lifecycleOwner.getLifecycle().removeObserver(this);
                    mNavigationSceneManager.removeOnBackPressedListener(onBackPressedListener);
                }
            }
        });
    }

    
    public void removeOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        ThreadUtility.checkUIThread();
        this.mNavigationSceneManager.removeOnBackPressedListener(onBackPressedListener);
    }

    
    public void addConfigurationChangedListener(final ILifecycle lifecycleOwner, final ConfigurationChangedListener configurationChangedListener) {
        Ability ability = getAbility();
        if (!Utility.isAbilityStatusValid(ability)) {
            return;
        }
        AbilityCompatibilityUtility.addConfigurationChangedListener((FractionAbility) ability, lifecycleOwner, configurationChangedListener);
    }

    
    public String getStackHistory() {
        return mNavigationSceneManager.getStackHistory();
    }

    
    public Scene getCurrentScene() {
        return mNavigationSceneManager.getCurrentScene();
    }

    
    @Override
    public List<Scene> getSceneList() {
        return mNavigationSceneManager.getCurrentSceneList();
    }

    public void setDefaultNavigationAnimationExecutor( NavigationAnimationExecutor defaultNavigationAnimationExecutor) {
        this.mDefaultNavigationAnimationExecutor = defaultNavigationAnimationExecutor;
    }

    
    public NavigationAnimationExecutor getDefaultNavigationAnimationExecutor() {
        return this.mDefaultNavigationAnimationExecutor;
    }

    public void setRootSceneComponentFactory( SceneComponentFactory rootSceneComponentFactory) {
        this.mRootSceneComponentFactory = rootSceneComponentFactory;
    }

    @Override
    public void disableSupportRestore() {
        this.mSupportRestore = false;
    }

    @Override
    public boolean isSupportRestore() {
        return this.mSupportRestore;
    }

    private void createRootSceneIfNeeded() {
        String clazzName = mNavigationSceneOptions.getRootSceneClassName();
        IntentParams arguments = mNavigationSceneOptions.getRootSceneArguments();

        Scene rootScene = null;
        if (this.mRootSceneComponentFactory != null) {
            ClassLoader classLoader = requireAbility().getClassloader();
            rootScene = this.mRootSceneComponentFactory.instantiateScene(classLoader, clazzName, arguments);
            if (rootScene != null && rootScene.getParentScene() != null) {
                throw new IllegalArgumentException("SceneComponentFactory instantiateScene return Scene already has a parent");
            }
        }

        if (rootScene == null) {
            rootScene = SceneInstanceUtility.getInstanceFromClassName(requireAbility(), clazzName, arguments);
        }
        mNavigationSceneManager.push(rootScene, new PushOptions.Builder().build());
    }

    public void push(Class<? extends Scene> clazz) {
        push(clazz, null, new PushOptions.Builder().build());
    }

    public void push(Class<? extends Scene> clazz, IntentParams argument) {
        push(clazz, argument, new PushOptions.Builder().build());
    }

    void addToReusePool(ReuseGroupScene scene) {
        mLruCache.put(scene.getClass(), scene);
    }

    /**
     * Push a new Scene.
     *
     * @param clazz 场景
     * @param argument 参数
     * @param pushOptions 跳转配置
     */
    public void push(Class<? extends Scene> clazz,  IntentParams argument,  PushOptions pushOptions) {
        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }

        Scene scene = null;
        if (ReuseGroupScene.class.isAssignableFrom(clazz)) {
            scene = mLruCache.get(clazz);
        }

        if (scene == null) {
            scene = SceneInstanceUtility.getInstanceFromClass(clazz, argument);
        } else {
            if (argument != null) {
                scene.setArguments(argument);
            }
        }

        push(scene, pushOptions);
    }

    public void push(Scene scene) {
        push(scene, new PushOptions.Builder().build());
    }

    public void push(Scene scene,  PushOptions pushOptions) {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }

        if (scene.getParentScene() != null) {
//            if (scene.getParentScene() == this) {
//                throw new IllegalArgumentException("Scene is already pushed");
//            }
//            throw new IllegalArgumentException("Scene already has a parent, parent " + scene.getParentScene());
        }

        if (isSupportRestore() && !SceneInstanceUtility.isSupportRestore(scene)) {
            throw new IllegalArgumentException("Scene " + scene.getClass().getName() + " must be a public class or public static class, " +
                    "and have only one parameterless constructor to be properly recreated from instance state.");
        }

        hideSoftInputIfNeeded();
        cancelPendingInputEventsIfNeeded();
        mNavigationSceneManager.push(scene, pushOptions);
    }

    private void hideSoftInputIfNeeded() {
        Scene currentScene = mNavigationSceneManager.getCurrentScene();
        if (currentScene != null) {
            SoftInputUtility.hideSoftInputFromWindow(currentScene.getView());
        }
    }

    private void cancelPendingInputEventsIfNeeded() {
    }

    public void setResult(Scene scene,  Object result) {
        mNavigationSceneManager.setResult(scene, result);
    }

    public boolean onBackPressed() {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return false;
        }
        if (mNavigationSceneManager.interceptOnBackPressed()) {
            return true;
        } else if (mNavigationSceneManager.canPop()) {
            pop();
            return true;
        } else {
            return false;
        }
    }

    public void pop() {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }
        hideSoftInputIfNeeded();
        cancelPendingInputEventsIfNeeded();
        mNavigationSceneManager.pop();
    }

    @Override
    public String beginSuppressStackOperation(String tagPrefix) {
        return mNavigationSceneManager.beginSuppressStackOperation(tagPrefix);
    }

    @Override
    public void endSuppressStackOperation(String suppressTag) {
        mNavigationSceneManager.endSuppressStackOperation(suppressTag);
    }

    void finishCurrentAbility() {
        requireAbility().terminateAbility();
    }

    public void pop(PopOptions popOptions) {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }
        hideSoftInputIfNeeded();
        cancelPendingInputEventsIfNeeded();
        mNavigationSceneManager.pop(popOptions);
    }

    public void popTo(Class<? extends Scene> clazz) {
        popTo(clazz, null);
    }

    public void popTo(Class<? extends Scene> clazz, NavigationAnimationExecutor animationFactory) {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }
        hideSoftInputIfNeeded();
        cancelPendingInputEventsIfNeeded();
        mNavigationSceneManager.popTo(clazz, animationFactory);
    }

    public void popToRoot() {
        popToRoot(null);
    }

    public void popToRoot(NavigationAnimationExecutor animationFactory) {
        ThreadUtility.checkUIThread();
        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }
        hideSoftInputIfNeeded();
        cancelPendingInputEventsIfNeeded();
        mNavigationSceneManager.popToRoot(animationFactory);
    }

    public void remove(Scene scene) {
        ThreadUtility.checkUIThread();

        if (!Utility.isAbilityStatusValid(getAbility())) {
            return;
        }
        if (mNavigationSceneManager.getCurrentScene() == scene) {
            hideSoftInputIfNeeded();
            cancelPendingInputEventsIfNeeded();
        }
        mNavigationSceneManager.remove(scene);
    }

    public void requestDisableTouchEvent(boolean disable) {
        ((NavigationFrameLayout) getView()).setTouchEnabled(!disable);
    }

    public void overrideNavigationAnimationExecutor(Scene scene,  NavigationAnimationExecutor navigationAnimationExecutor) {
        ThreadUtility.checkUIThread();
        if (scene.getLifecycle().getLifecycleState() == Lifecycle.Event.ON_STOP) {
            // ignore
            return;
        }
        Record record = mNavigationSceneManager.findRecordByScene(scene);
        if (record != null) {
            record.mNavigationAnimationExecutor = navigationAnimationExecutor;
        }
    }

    /**
     * {@link PushOptions.Builder#setAnimation(NavigationAnimationExecutor)}
     * {@link #overrideNavigationAnimationExecutor(Scene, NavigationAnimationExecutor)}
     *
     * @param scene 场景
     * @return 导航动画
     */
    public NavigationAnimationExecutor getNavigationAnimationExecutor(Scene scene) {
        Record record = mNavigationSceneManager.findRecordByScene(scene);
        if (record != null) {
            return record.mNavigationAnimationExecutor;
        } else {
            return null;
        }
    }

    public ComponentContainer getSceneContainer() {
        return this.mSceneContainer;
    }

    public ComponentContainer getAnimationContainer() {
        return this.mAnimationContainer;
    }

    @Override
    public void navigationChange( Scene from, Scene to, boolean isPush) {
        List<NavigationListener> listenerList = new ArrayList<>(mNavigationListenerList);
        for (NavigationListener listener : listenerList) {
            listener.navigationChange(from, to, isPush);
        }
    }

    @Override
    public final void dispatchAttachScene( Scene parentScene) {
        super.dispatchAttachScene(parentScene);
        if (parentScene == null) {
            //ignore
        } else if (parentScene instanceof SceneParent) {
            SceneParent sceneParent = (SceneParent) parentScene;
            if (!sceneParent.isSupportRestore()) {
                disableSupportRestore();
            }
        } else {
            throw new SceneInternalException("unknown parent Scene type " + parentScene.getClass());
        }
    }

    @Override
    public void onAttach() {
        super.onAttach();
    }


    @Override
    public void onCreate(Intent savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.mNavigationSceneManager = new NavigationSceneManager(this);

        IntentParams arguments = getArguments();
        if (arguments == null) {
            throw new IllegalArgumentException("NavigationScene need NavigationSceneOptions bundle");
        }
        mNavigationSceneOptions = NavigationSceneOptions.fromBundle(new Intent().setParams(arguments));
        if (savedInstanceState != null) {
            boolean supportRestore = savedInstanceState.getBooleanParam(KEY_NAVIGATION_SCENE_SUPPORT_RESTORE_ARGUMENT, isSupportRestore());
            if (!supportRestore) {
                disableSupportRestore();
            }
        }
    }

    
    @Override
    public Component onCreateView(LayoutScatter inflater, ComponentContainer container, Intent savedInstanceState) {
        setInstance(this);
        NavigationFrameLayout stackLayout = new NavigationFrameLayout(requireSceneContext());

        mSceneContainer = new StackLayout(requireSceneContext());
        stackLayout.addComponent(mSceneContainer, new StackLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
                ComponentContainer.LayoutConfig.MATCH_PARENT));

        mAnimationContainer = new AnimationContainerLayout(requireSceneContext());
        stackLayout.addComponent(mAnimationContainer, new StackLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
                ComponentContainer.LayoutConfig.MATCH_PARENT));

        return stackLayout;
    }

    @Override
    public void onAbilityCreated(Intent savedInstanceState) {
        super.onAbilityCreated(savedInstanceState);
//        ArrayList<Intent> bundleList = savedInstanceState.getSequenceableArrayListParam(ParcelConstants.KEY_NAVIGATION_SCENE_MANAGER_TAG);
//        if (bundleList != null && isSupportRestore()) {
//            this.mNavigationSceneManager.restoreFromBundle(requireAbility(), savedInstanceState, this.mRootSceneComponentFactory);
//        } else {
//            createRootSceneIfNeeded();
//        }
        createRootSceneIfNeeded();

        NavigationScene parentSceneNavigation = NavigationSceneGetter.getNavigationScene(this);
        if (parentSceneNavigation != null) {
            parentSceneNavigation.addOnBackPressedListener(this, new OnBackPressedListener() {
                @Override
                public boolean onBackPressed() {
                    return NavigationScene.this.onBackPressed();
                }
            });
        }
    }

    @Override
    public void dispatchAbilityCreated(Intent savedInstanceState) {
        super.dispatchAbilityCreated(savedInstanceState);
        this.mNavigationSceneManager.executePendingOperation();
    }

    @Override
    public void dispatchStart() {
        super.dispatchStart();
        dispatchCurrentChildState(State.STARTED);
    }

    @Override
    public void dispatchResume() {
        super.dispatchResume();
        dispatchCurrentChildState(State.RESUMED);
    }

    @Override
    public void dispatchPause() {
        dispatchCurrentChildState(State.STARTED);
        super.dispatchPause();
    }

    @Override
    public void onSaveInstanceState(Intent outState) {
        super.onSaveInstanceState(outState);
        if (outState.hasParameter(KEY_NAVIGATION_SCENE_SUPPORT_RESTORE_ARGUMENT)) {
            throw new IllegalArgumentException("outState already contains key " + KEY_NAVIGATION_SCENE_SUPPORT_RESTORE_ARGUMENT);
        } else {
            outState.setParam(KEY_NAVIGATION_SCENE_SUPPORT_RESTORE_ARGUMENT, isSupportRestore());
            if (isSupportRestore()) {
                this.mNavigationSceneManager.saveToBundle(outState);
            }
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        /*
         * Terminate the animation to avoid the possibility of going after the onDestroyView
         * Otherwise, if the animation is still executing, it will cause a crash or memory leak.
         */
        this.mNavigationSceneManager.cancelCurrentRunningAnimation();
    }

    @Override
    public void dispatchStop() {
        dispatchCurrentChildState(State.ABILITY_CREATED);
        super.dispatchStop();
    }

    @Override
    public void onDestroyView() {
        dispatchChildrenState(State.NONE, true);
        super.onDestroyView();
    }

    private void dispatchCurrentChildState(State state) {
        if (getState().value < State.VIEW_CREATED.value) {
            throw new IllegalArgumentException("dispatchCurrentChildState can only call when state is VIEW_CREATED, ABILITY_CREATED, STARTED, RESUMED");
        }
        mNavigationSceneManager.dispatchCurrentChildState(state);
    }

    private void dispatchChildrenState(State state, boolean reverseOrder) {
        mNavigationSceneManager.dispatchChildrenState(state, reverseOrder);
    }

    Record findRecordByScene(Scene scene) {
        return mNavigationSceneManager.findRecordByScene(scene);
    }

    public void convertFromTranslucent(Scene scene) {

    }

    public boolean convertToTranslucent(Scene scene) {
        return false;
    }

    public void convertBackgroundToBlack() {
        ShapeElement shapeElement=new ShapeElement();
        shapeElement.setRgbColor(new RgbColor(0,0,0));
        getView().setBackground(shapeElement);
    }

    public void convertBackgroundToDefault() {
        if (mNavigationSceneOptions.drawWindowBackground()) {

        }
    }

    public void startAbilityForResult(Intent intent, int requestCode, AbilityResultCallback resultCallback) {
        Ability ability = getAbility();
        if (!Utility.isAbilityStatusValid(ability)) {
            return;
        }
//        AbilityCompatibilityUtility.startAbilityForResult((FractionAbility) ability, this, intent, requestCode, resultCallback);
        setResultCallback(resultCallback);

        Intent mIntent=new Intent();
        Operation operation=new Intent.OperationBuilder()
                .withBundleName(ability.getBundleName())
                .withAbilityName(EmptyAbility.class)
                .build();
        mIntent.setOperation(operation);
        mIntent.setParam("requestCode",requestCode);
        mIntent.setParam("requestIntent",intent);
        mIntent.setParam("type",intent.getType());//使用传值方式，传Intent，接收方在Intent取不type
        ability.startAbility(mIntent);
    }

    public void startAbility(Intent intent) {
        Ability ability = getAbility();
        if (!Utility.isAbilityStatusValid(ability)) {
            return;
        }
        ability.startAbility(intent);
    }

    public void requestPermissions(String[] permissions, int requestCode, PermissionResultCallback resultCallback) {
        Ability ability = getAbility();
//        if (!Utility.isAbilityStatusValid(ability)) {
//            return;
//        }
//        AbilityCompatibilityUtility.requestPermissions((FractionAbility) ability, this, permissions, requestCode, resultCallback);

        this.permissionResultCallback = resultCallback;
        Intent mIntent=new Intent();
        Operation operation=new Intent.OperationBuilder()
                .withBundleName(ability.getBundleName())
                .withAbilityName(EmptyAbility.class)
                .build();
        mIntent.setOperation(operation);
        mIntent.setParam("requestCode",requestCode);
        ability.startAbility(mIntent);
    }


    public void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
        System.out.println("=======NavigationScene.onAbilityResult========requestCode="+requestCode);
        if(getResultCallback()!=null){
            getResultCallback().onResult(resultCode,resultData);
        }
    }

    public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
        System.out.println("=======onRequestPermissionsFromUserResult.onAbilityResult========requestCode="+requestCode);
        if (permissionResultCallback!=null) {
            permissionResultCallback.onResult(grantResults);
        }
    }



    public boolean pop(final InteractionNavigationPopAnimationFactory animationFactory) {
        ThreadUtility.checkUIThread();

        animationFactory.setCallback(mInteractionCallback);
        boolean result = mNavigationSceneManager.pop(animationFactory);
        if (!result) {
            //clear callback
            animationFactory.setCallback(null);
        }
        return result;
    }

    private InteractionNavigationPopAnimationFactory.InteractionCallback mInteractionCallback = new InteractionNavigationPopAnimationFactory.InteractionCallback() {
        @Override
        public void onStart() {
            List<InteractionNavigationPopAnimationFactory.InteractionCallback> copy = new ArrayList<>(mInteractionListenerList);
            for (InteractionNavigationPopAnimationFactory.InteractionCallback callback : copy) {
                callback.onStart();
            }
        }

        @Override
        public void onProgress(float progress) {
            List<InteractionNavigationPopAnimationFactory.InteractionCallback> copy = new ArrayList<>(mInteractionListenerList);
            for (InteractionNavigationPopAnimationFactory.InteractionCallback callback : copy) {
                callback.onProgress(progress);
            }
        }

        @Override
        public void onFinish() {
            List<InteractionNavigationPopAnimationFactory.InteractionCallback> copy = new ArrayList<>(mInteractionListenerList);
            for (InteractionNavigationPopAnimationFactory.InteractionCallback callback : copy) {
                callback.onFinish();
            }
        }
    };

    public boolean isInteractionNavigationPopSupport(InteractionNavigationPopAnimationFactory animationFactory) {
        return mNavigationSceneManager.isInteractionNavigationPopSupport(animationFactory);
    }

    public void registerInteractionCallback(InteractionNavigationPopAnimationFactory.InteractionCallback callback) {
        ThreadUtility.checkUIThread();
        this.mInteractionListenerList.add(callback);
    }

    public void unregisterInteractionCallback(InteractionNavigationPopAnimationFactory.InteractionCallback callback) {
        ThreadUtility.checkUIThread();
        this.mInteractionListenerList.remove(callback);
    }

    public void registerChildSceneLifecycleCallbacks(ChildSceneLifecycleCallbacks cb, boolean recursive) {
        ThreadUtility.checkUIThread();
        this.mLifecycleCallbacks.add(NonNullPair.create(cb, recursive));
    }

    public void unregisterChildSceneLifecycleCallbacks(ChildSceneLifecycleCallbacks cb) {
        ThreadUtility.checkUIThread();
        NonNullPair<ChildSceneLifecycleCallbacks, Boolean> target = null;
        for (int i = 0, N = this.mLifecycleCallbacks.size(); i < N; i++) {
            if (this.mLifecycleCallbacks.get(i).first == cb) {
                target = this.mLifecycleCallbacks.get(i);
                break;
            }
        }
        if (target != null) {
            this.mLifecycleCallbacks.remove(target);
        }
    }

    @Override
    
    public String getSceneDebugInfo(Scene scene) {
        if (scene.getParentScene() == null) {
            return null;
        }
        if (scene.getParentScene() != this) {
            throw new IllegalArgumentException("Scene parent is incorrect");
        }
        Lifecycle.Event state = scene.getLifecycle().getLifecycleState();
        String status = null;
        if (state == Lifecycle.Event.ON_ACTIVE) {
            status = "resumed";
        } else if (state == Lifecycle.Event.ON_START) {
            status = "stopped";
        }
        return "status: " + status + " ";
    }

    @Override
    public void dispatchOnSceneCreated(Scene scene,  Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneCreated(scene, savedInstanceState);
                }
            }
        }

        super.dispatchOnSceneCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public final void dispatchOnSceneViewCreated(Scene scene,  Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneViewCreated(scene, savedInstanceState);
                }
            }
        }

        super.dispatchOnSceneViewCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public void dispatchOnSceneAbilityCreated(Scene scene,  Intent savedInstanceState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneAbilityCreated(scene, savedInstanceState);
                }
            }
        }
        super.dispatchOnSceneAbilityCreated(scene, savedInstanceState, directChild);
    }

    @Override
    public void dispatchOnSceneStarted(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneStarted(scene);
                }
            }
        }

        super.dispatchOnSceneStarted(scene, directChild);
    }

    @Override
    public void dispatchOnSceneResumed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneResumed(scene);
                }
            }
        }

        super.dispatchOnSceneResumed(scene, directChild);
    }

    @Override
    public void dispatchOnSceneStopped(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneStopped(scene);
                }
            }
        }
        super.dispatchOnSceneStopped(scene, directChild);
    }

    @Override
    public void dispatchOnScenePaused(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onScenePaused(scene);
                }
            }
        }

        super.dispatchOnScenePaused(scene, directChild);
    }

    @Override
    public void dispatchOnSceneSaveInstanceState(Scene scene, Intent outState, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneSaveInstanceState(scene, outState);
                }
            }
        }

        super.dispatchOnSceneSaveInstanceState(scene, outState, directChild);
    }

    @Override
    public void dispatchOnSceneViewDestroyed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneViewDestroyed(scene);
                }
            }
        }

        super.dispatchOnSceneViewDestroyed(scene, directChild);
    }

    @Override
    public final void dispatchOnSceneDestroyed(Scene scene, boolean directChild) {
        if (scene != this) {
            List<NonNullPair<ChildSceneLifecycleCallbacks, Boolean>> list = new ArrayList<>(mLifecycleCallbacks);
            for (NonNullPair<ChildSceneLifecycleCallbacks, Boolean> pair : list) {
                if (directChild || pair.second) {
                    pair.first.onSceneDestroyed(scene);
                }
            }
        }

        super.dispatchOnSceneDestroyed(scene, directChild);
    }


}
